<template>
  <div>
    <!-- 
      拖拽功能
      draggable：拖拽开关
      active-text：功能开启时显示字样
      inactive-text：功能关闭时显示字样
     -->
    <el-switch
      v-model="draggable"
      active-text="开启拖拽"
      inactive-text="关闭拖拽"
    ></el-switch>
    <!-- 开启拖拽，按钮显示 -->
    <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
    <!-- 节点批量删除 -->
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <!-- 
      expand-on-click-node：是否在点击节点的时候展开或收缩节点，默认为true，如果为false，则只有点箭头图标才会展开或收缩
      show-checkbox：节点是否可以被选择
      node-key：每个树节点用来作为唯一标识的属性，它在整棵树中是唯一的
      default-expanded-keys：默认展开节点数组，这里采用动态绑定
      draggable：是否开启拖拽节点功能
      allow-drop：拖拽时判定目标节点能否被放置。
      type 参数有三种情况：'prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
      node-drop：监听拖拽成功事件
      ref：树组件的唯一标识
     -->
    <el-tree
      :data="menus"
      :props="defaultProps"
      :expand-on-click-node="false"
      show-checkbox
      node-key="catId"
      :default-expanded-keys="expandedKey"
      :draggable="draggable"
      :allow-drop="allowDrop"
      @node-drop="handleDrop"
      ref="menuTree"
    >
      <!-- 
      插槽机制实现增、删、改功能。
      node：当前节点。  
      data：真正节点数据。
      实现原理：通过按钮来传 node 或 data数据给方法，然后通过方法和后端代码通信
       -->
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <!-- 
            添加按钮
            1级和2级菜单可以实现添加功能 
          -->
          <el-button
            v-if="node.level <= 2"
            type="text"
            size="mini"
            @click="() => append(data)"
            >添加</el-button
          >
          <!-- 编辑按钮 -->
          <el-button type="text" size="mini" @click="edit(data)"
            >编辑</el-button
          >
          <!-- 
            删除按钮
            没有子节点才可以删除
          -->
          <el-button
            v-if="node.childNodes.length == 0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
            >删除</el-button
          >
        </span>
      </span>
    </el-tree>
    <!-- 
      有嵌套表单的对话框
      visible.sync：对话框是否显示
      close-on-click-modal：设置为 false 后，点击对话框外的区域，对话框不会消失
    -->
    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <!-- 
        对话框中的表单
        model：表单中的数据对象
      -->
      <el-form :model="category">
        <!-- 
          表单项
          label：表单标签
        -->
        <el-form-item label="分类名称">
          <!-- 
            表单输入框
            v-model：表单输入框绑定的属性
          -->
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="category.productUnit"
            autocomplete="off"
          ></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitData">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
//这里可以导入其他文件（比如：组件，工具js，第三方插件js，json文件，图片文件等等）
//例如：import 《组件名称》 from '《组件路径》';

export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      pCid: [], // 所有要展开的父节点
      draggable: false, // 拖拽开关,默认不可拖拽
      updateNodes: [], // 所有要修改的节点
      maxLevel: 0, // 某个节点的最大层级
      title: "",
      dialogType: "", // 对话框类型： edit,add
      // 表单对象
      category: {
        name: "", // 分类名称
        parentCid: 0, // 父分类id
        catLevel: 0, // 层级
        showStatus: 1, // 是否显示[0-不显示，1显示]
        sort: 0, // 排序
        productUnit: "", // 计量单位
        icon: "", // 图标地址
        catId: null, // 分类id
      },
      dialogVisible: false, // 对话框默认关闭
      menus: [], // 菜单列表数据
      expandedKey: [], // 展开的节点数组
      defaultProps: {
        children: "children", // 哪个字段表示子节点
        label: "name", // 哪个字段用来显示
      },
    };
  },

  //计算属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  // 方法集合
  methods: {
    // 获取树形菜单结构
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        // 把返回结果中包含的 data 解构出来
        // data.data 菜单数据列表
        console.log("成功获取到菜单数据...", data.data);
        this.menus = data.data;
      });
    },
    // 批量删除
    batchDelete() {
      let catIds = [];
      // 若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点所组成的数组
      let checkedNodes = this.$refs.menuTree.getCheckedNodes();
      console.log("被选中的元素", checkedNodes);
      for (let i = 0; i < checkedNodes.length; i++) {
        catIds.push(checkedNodes[i].catId);
      }
      this.$confirm(`是否批量删除【${catIds}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        // 确定按钮
        .then(() => {
          // 发送删除请求
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(catIds, false),
          }).then(({ data }) => {
            this.$message({
              message: "菜单批量删除成功",
              type: "success",
            });
            // 菜单刷新
            this.getMenus();
          });
        })
        // 取消按钮
        .catch(() => {});
    },
    /* 批量保存分类 */
    batchSave() {
      // 发送请求
      this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单顺序等修改成功",
          type: "success",
        });
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey = this.pCid;
        // 清空数据
        this.updateNodes = [];
        this.maxLevel = 0;
        // this.pCid = 0;
      });
    },
    /* 
      监听成功拖拽：共四个参数
      draggingNode：被拖拽节点对应的 Node
      dropNode：结束拖拽时最后进入的节点
      dropType被拖拽节点的放置位置（before、after、inner）
      event
    */
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新的父节点id
      let pCid = 0;
      // 当前节点拖拽后最新的兄弟
      let siblings = null;
      // draggingNode 节点在 dropNode节点的前面或后面
      if (dropType == "before" || dropType == "after") {
        pCid =
          dropNode.parent.data.catId == undefined // dropNode无父节点
            ? 0 // 父节点为0
            : dropNode.parent.data.catId; // 父节点为dropNode的父节点
        siblings = dropNode.parent.childNodes; // 兄弟为dropNode父节点的孩子
      } else {
        pCid = dropNode.data.catId; // 父节点为dropNode
        siblings = dropNode.childNodes; // 兄弟为dropNode的孩子
      }
      this.pCid.push(pCid); // 每次拖拽都要保存父节点

      //2、当前拖拽节点的最新顺序，
      for (let i = 0; i < siblings.length; i++) {
        // 如果遍历的是当前正在拖拽的节点
        if (siblings[i].data.catId == draggingNode.data.catId) {
          let catLevel = draggingNode.level;
          if (siblings[i].level != draggingNode.level) {
            // 当前节点的层级发生变化
            catLevel = siblings[i].level;
            // 修改他子节点的层级
            this.updateChildNodeLevel(siblings[i]);
          }
          this.updateNodes.push({
            catId: siblings[i].data.catId, // 要修改哪个节点
            sort: i, // 排序
            parentCid: pCid, // 父节点
            catLevel: catLevel, // 层级
          });
        } else {
          this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
        }
      }

      //3、当前拖拽节点的最新层级
      console.log("updateNodes", this.updateNodes);
    },
    /* 修改孩子节点层级 */
    updateChildNodeLevel(node) {
      if (node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          var cNode = node.childNodes[i].data;
          this.updateNodes.push({
            catId: cNode.catId, // 要修改哪个子节点
            catLevel: node.childNodes[i].level, // 子节点层级同步
          });
          this.updateChildNodeLevel(node.childNodes[i]); // 递归调用子节点的子节点层级
        }
      }
    },

    /*
       draggingNode:被拖拽的当前节点
       dropNode：被拖拽的目标节点
       type：拖拽类型：'prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
     */
    allowDrop(draggingNode, dropNode, type) {
      console.log("allowDrop:", draggingNode, dropNode, type);
      // 计算被拖动的当前节点孩子节点中最大层数
      this.countNodeLevel(draggingNode);

      // 被拖动节点的深度（被拖动节点到最大层数孩子节点的层数）
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("深度：", deep);

      // 拖到目标节点内部
      if (type == "inner") {
        // console.log(
        //   `this.maxLevel：${this.maxLevel}；draggingNode.data.catLevel：${draggingNode.data.catLevel}；dropNode.level：${dropNode.level}`
        // );
        // 当前正在拖动的节点+父节点所在的深度不大于3即可
        return deep + dropNode.level <= 3;
      } else {
        // 拖到目标节点的签名或后面
        return deep + dropNode.parent.level <= 3;
      }
    },
    /* 计算被拖动的当前节点孩子节点中最大层数 */
    countNodeLevel(node) {
      // 找到所有子节点中最大的层级
      if (node.childNodes != null && node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          if (node.childNodes[i].level > this.maxLevel) {
            this.maxLevel = node.childNodes[i].level;
          }
          this.countNodeLevel(node.childNodes[i]);
        }
      }
    },
    // 实现修改数据填充
    edit(data) {
      console.log("要修改的数据", data);
      this.dialogType = "edit"; // 对话框类型
      this.title = "修改分类"; // 对话框标题
      this.dialogVisible = true; // 显示修改对话框

      // 每次回显都要发送请求获取当前节点最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get",
      }).then(({ data }) => {
        // 请求成功，填充待修改的分类数据
        console.log("要回显的数据", data);
        this.category.name = data.data.name; // 分类名称
        this.category.catId = data.data.catId; // 分类id
        this.category.icon = data.data.icon; // 图标地址
        this.category.productUnit = data.data.productUnit; // 计量单位
        this.category.parentCid = data.data.parentCid; // 父分类id
        this.category.catLevel = data.data.catLevel; // 层级
        this.category.sort = data.data.sort; // 排序
        this.category.showStatus = data.data.showStatus; // 是否显示
      });
    },
    /* 实现添加分类数据填充  */
    append(data) {
      console.log("append", data);
      this.dialogType = "add"; // 对话框类型
      this.title = "添加分类"; // 对话框标题
      this.dialogVisible = true; // 打开对话框
      this.category.parentCid = data.catId; // 父分类id
      this.category.catLevel = data.catLevel * 1 + 1; // 分类层级
      this.category.catId = null; // 分类id
      this.category.name = ""; // 分类名称
      this.category.icon = ""; // 分类图标
      this.category.productUnit = ""; // 分类计量单位
      this.category.sort = 0; // 分类排序
      this.category.showStatus = 1; // 是否显示分类
    },
    /* 添加或修改表单 */
    submitData() {
      // 添加表单
      if (this.dialogType == "add") {
        this.addCategory();
      }
      // 修改表单
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
    // 修改三级分类数据
    editCategory() {
      // 解构下面4项，只发这4项
      var { catId, name, icon, productUnit } = this.category;
      //  发送修改请求
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        // 只发下面4项数据，因为只修改这4个数据
        data: this.$http.adornData({ catId, name, icon, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单修改成功",
          type: "success",
        });
        // 关闭对话框
        this.dialogVisible = false;
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    // 添加三级分类
    addCategory() {
      console.log("提交的三级分类数据", this.category);
      // 请求后台，保存添加的数据
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false),
      }).then(({ data }) => {
        this.$message({
          message: "菜单保存成功",
          type: "success",
        });
        // 数据保存成功后，关闭对话框
        this.dialogVisible = false;
        // 刷新出新的菜单
        this.getMenus();
        // 设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    /* 实现菜单删除功能 */
    remove(node, data) {
      // 待删除节点id
      var ids = [data.catId];
      // 删除前，弹出确认提示框，如果要引用数据，需要用到插值表达式，此时注意要用反引号引起来
      this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          // 点击确定按钮走这个分支
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false),
          }).then(({ data }) => {
            // 把返回结果中包含的 data 解构出来
            // 删除成功提示消息
            this.$message({
              message: "菜单删除成功",
              type: "success",
            });
            // 刷新出新的菜单
            this.getMenus();
            // 设置需要默认展开的菜单(删除节点的父节点要展开)
            this.expandedKey = [node.parent.data.catId];
          });
        }) // 点击取消按钮走这个分支
        .catch(() => {});

      console.log("remove", node, data);
    },
  },
  // 生命周期 - 创建完成（可以访问当前this实例）
  created() {
    // 获得菜单数据
    this.getMenus();
  },
  //生命周期 - 挂载完成（可以访问DOM元素）
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {}, //如果页面有keep-alive缓存功能，这个函数会触发
};
</script>
<style scoped>
</style>